/**
 * Scintilla source code edit control
 * PlatCocoa.mm - implementation of platform facilities on MacOS X/Cocoa
 *
 * Written by Mike Lischke
 * Based on PlatMacOSX.cxx
 * Based on work by Evan Jones (c) 2002 <ejones@uwaterloo.ca>
 * Based on PlatGTK.cxx Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
 * The License.txt file describes the conditions under which this software may be distributed.
 *
 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
 * This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt).
 */

#import <ScintillaView.h>

#include "PlatCocoa.h"

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <assert.h>
#include <sys/time.h>
#include <stdexcept>

#include "XPM.h"

#import <Foundation/NSGeometry.h>

#import <Carbon/Carbon.h> // Temporary

using namespace Scintilla;

extern sptr_t scintilla_send_message(void* sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam);

//--------------------------------------------------------------------------------------------------

/**
 * Converts a PRectangle as used by Scintilla to standard Obj-C NSRect structure .
 */
NSRect PRectangleToNSRect(PRectangle& rc)
{
  return NSMakeRect(rc.left, rc.top, rc.Width(), rc.Height());
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts an NSRect as used by the system to a native Scintilla rectangle.
 */
PRectangle NSRectToPRectangle(NSRect& rc)
{
  return PRectangle(rc.origin.x, rc.origin.y, rc.size.width + rc.origin.x, rc.size.height + rc.origin.y);
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts a PRctangle as used by Scintilla to a Quartz-style rectangle.
 */
inline CGRect PRectangleToCGRect(PRectangle& rc)
{
  return CGRectMake(rc.left, rc.top, rc.Width(), rc.Height());
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts a Quartz-style rectangle to a PRectangle structure as used by Scintilla.
 */
inline PRectangle CGRectToPRectangle(const CGRect& rect)
{
  PRectangle rc;
  rc.left = (int)(rect.origin.x + 0.5);
  rc.top = (int)(rect.origin.y + 0.5);
  rc.right = (int)(rect.origin.x + rect.size.width + 0.5);
  rc.bottom = (int)(rect.origin.y + rect.size.height + 0.5);
  return rc;
}

//----------------- Point --------------------------------------------------------------------------

/**
 * Converts a point given as a long into a native Point structure.
 */
Scintilla::Point Scintilla::Point::FromLong(long lpoint)
{
  return Scintilla::Point(
                          Platform::LowShortFromLong(lpoint),
                          Platform::HighShortFromLong(lpoint)
                          );
}

//----------------- Palette ------------------------------------------------------------------------

// The Palette implementation is only here because we would get linker errors if not.
// We don't use indexed colors in ScintillaCocoa.

Scintilla::Palette::Palette()
{
}

//--------------------------------------------------------------------------------------------------

Scintilla::Palette::~Palette()
{
}

//--------------------------------------------------------------------------------------------------

void Scintilla::Palette::Release()
{
}

//--------------------------------------------------------------------------------------------------

/**
 * Used to transform a given color, if needed. If the caller tries to find a color that matches the
 * desired color then we simply pass it on, as we support the full color space.
 */
void Scintilla::Palette::WantFind(ColourPair &cp, bool want)
{
  if (!want)
    cp.allocated.Set(cp.desired.AsLong());
  
  // Don't do anything if the caller wants the color it has already set.
}

//--------------------------------------------------------------------------------------------------

void Scintilla::Palette::Allocate(Window& w)
{
  // Nothing to allocate as we don't use palettes.
}

//----------------- Font ---------------------------------------------------------------------------

Font::Font(): fid(0)
{
}

//--------------------------------------------------------------------------------------------------

Font::~Font()
{
  Release();
}

//--------------------------------------------------------------------------------------------------

/**
 * Creates a Quartz 2D font with the given properties.
 * TODO: rewrite to use NSFont.
 */
void Font::Create(const char *faceName, int characterSet, int size, bool bold, bool italic, 
                  int extraFontFlag)
{
  // TODO: How should I handle the characterSet request?
  Release();
  
  QuartzTextStyle* style = new QuartzTextStyle();
  fid = style;
  
  // Find the font
  QuartzFont font(faceName, strlen(faceName));
  
  // We set Font, Size, Bold, Italic
  QuartzTextSize textSize(size);
  QuartzTextBold isBold(bold);
  QuartzTextItalic isItalic(italic);
  
  // Actually set the attributes
  QuartzTextStyleAttribute* attributes[] = { &font, &textSize, &isBold, &isItalic };
  style->setAttributes(attributes, sizeof(attributes) / sizeof(*attributes));
  style->setFontFeature(kLigaturesType, kCommonLigaturesOffSelector);
}

//--------------------------------------------------------------------------------------------------

void Font::Release()
{
  if (fid)
    delete reinterpret_cast<QuartzTextStyle*>( fid );
  fid = 0;
}

//----------------- SurfaceImpl --------------------------------------------------------------------

SurfaceImpl::SurfaceImpl()
{
  bitmapData = NULL; // Release will try and delete bitmapData if != NULL
  gc = NULL;
  textLayout = new QuartzTextLayout(NULL);
  Release();
}

//--------------------------------------------------------------------------------------------------

SurfaceImpl::~SurfaceImpl()
{
  Release();
  delete textLayout;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Release()
{
  textLayout->setContext (NULL);
  if ( bitmapData != NULL )
  {
    delete[] bitmapData;
    // We only "own" the graphics context if we are a bitmap context
    if (gc != NULL)
      CGContextRelease(gc);
  }
  bitmapData = NULL;
  gc = NULL;
  
  bitmapWidth = 0;
  bitmapHeight = 0;
  x = 0;
  y = 0;
}

//--------------------------------------------------------------------------------------------------

bool SurfaceImpl::Initialised()
{
  // We are initalised if the graphics context is not null
  return gc != NULL;// || port != NULL;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Init(WindowID wid)
{
  // To be able to draw, the surface must get a CGContext handle.  We save the graphics port,
  // then aquire/release the context on an as-need basis (see above).
  // XXX Docs on QDBeginCGContext are light, a better way to do this would be good.
  // AFAIK we should not hold onto a context retrieved this way, thus the need for
  // aquire/release of the context.
  
  Release();
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Init(SurfaceID sid, WindowID wid)
{
  Release();
  gc = reinterpret_cast<CGContextRef>(sid);
  CGContextSetLineWidth(gc, 1.0);
  textLayout->setContext(gc);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::InitPixMap(int width, int height, Surface* surface_, WindowID wid)
{
  Release();
  
  // Create a new bitmap context, along with the RAM for the bitmap itself
  bitmapWidth = width;
  bitmapHeight = height;
  
  const int bitmapBytesPerRow = (width * BYTES_PER_PIXEL);
  const int bitmapByteCount = (bitmapBytesPerRow * height);
  
  // Create an RGB color space.
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  if (colorSpace == NULL)
    return;
  
  // Create the bitmap.
  bitmapData = new uint8_t[bitmapByteCount];
  if (bitmapData != NULL)
  {
    // create the context
    gc = CGBitmapContextCreate(bitmapData,
                               width,
                               height,
                               BITS_PER_COMPONENT,
                               bitmapBytesPerRow,
                               colorSpace,
                               kCGImageAlphaPremultipliedLast);
    
    if (gc == NULL)
    {
      // the context couldn't be created for some reason,
      // and we have no use for the bitmap without the context
      delete[] bitmapData;
      bitmapData = NULL;
    }
    textLayout->setContext (gc);
  }
  
  // the context retains the color space, so we can release it
  CGColorSpaceRelease(colorSpace);
  
  if (gc != NULL && bitmapData != NULL)
  {
    // "Erase" to white.
    CGContextClearRect( gc, CGRectMake( 0, 0, width, height ) );
    CGContextSetRGBFillColor( gc, 1.0, 1.0, 1.0, 1.0 );
    CGContextFillRect( gc, CGRectMake( 0, 0, width, height ) );
  }
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::PenColour(ColourAllocated fore)
{
  if (gc)
  {
    ColourDesired colour(fore.AsLong());
    
    // Set the Stroke color to match
    CGContextSetRGBStrokeColor(gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, 
                               colour.GetBlue() / 255.0, 1.0 );
  }
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::FillColour(const ColourAllocated& back)
{
  if (gc)
  {
    ColourDesired colour(back.AsLong());
    
    // Set the Fill color to match
    CGContextSetRGBFillColor(gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, 
                             colour.GetBlue() / 255.0, 1.0 );
  }
}

//--------------------------------------------------------------------------------------------------

CGImageRef SurfaceImpl::GetImage()
{
  // For now, assume that GetImage can only be called on PixMap surfaces.
  if (bitmapData == NULL)
    return NULL;
  
  CGContextFlush(gc);
  
  // Create an RGB color space.
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  if( colorSpace == NULL )
    return NULL;
  
  const int bitmapBytesPerRow = ((int) bitmapWidth * BYTES_PER_PIXEL);
  const int bitmapByteCount = (bitmapBytesPerRow * (int) bitmapHeight);
  
  // Create a data provider.
  CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, bitmapData, bitmapByteCount, 
                                                                NULL);
  CGImageRef image = NULL;
  if (dataProvider != NULL)
  {
    // Create the CGImage.
    image = CGImageCreate(bitmapWidth,
                          bitmapHeight,
                          BITS_PER_COMPONENT,
                          BITS_PER_PIXEL,
                          bitmapBytesPerRow,
                          colorSpace,
                          kCGImageAlphaPremultipliedLast,
                          dataProvider,
                          NULL,
                          0,
                          kCGRenderingIntentDefault);
  }
  
  // The image retains the color space, so we can release it.
  CGColorSpaceRelease(colorSpace);
  colorSpace = NULL;
  
  // Done with the data provider.
  CGDataProviderRelease(dataProvider);
  dataProvider = NULL;
  
  return image;
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the vertical logical device resolution of the main monitor.
 */
int SurfaceImpl::LogPixelsY()
{
  NSSize deviceResolution = [[[[NSScreen mainScreen] deviceDescription] 
                              objectForKey: NSDeviceResolution] sizeValue];
  return (int) deviceResolution.height;
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts the logical font height (in dpi) into a pixel height for the current main screen.
 */
int SurfaceImpl::DeviceHeightFont(int points)
{
  int logPix = LogPixelsY();
  return (points * logPix + logPix / 2) / 72;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::MoveTo(int x_, int y_)
{
  x = x_;
  y = y_;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::LineTo(int x_, int y_)
{
  CGContextBeginPath( gc );
  
  // Because Quartz is based on floating point, lines are drawn with half their colour
  // on each side of the line. Integer coordinates specify the INTERSECTION of the pixel
  // divison lines. If you specify exact pixel values, you get a line that
  // is twice as thick but half as intense. To get pixel aligned rendering,
  // we render the "middle" of the pixels by adding 0.5 to the coordinates.
  CGContextMoveToPoint( gc, x + 0.5, y + 0.5 );
  CGContextAddLineToPoint( gc, x_ + 0.5, y_ + 0.5 );
  CGContextStrokePath( gc );
  x = x_;
  y = y_;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Polygon(Scintilla::Point *pts, int npts, ColourAllocated fore,
                          ColourAllocated back)
{
  // Allocate memory for the array of points.
  CGPoint *points = new CGPoint[npts];
  
  for (int i = 0;i < npts;i++)
  {
    // Quartz floating point issues: plot the MIDDLE of the pixels
    points[i].x = pts[i].x + 0.5;
    points[i].y = pts[i].y + 0.5;
  }
  
  CGContextBeginPath(gc);
  
  // Set colours
  FillColour(back);
  PenColour(fore);
  
  // Draw the polygon
  CGContextAddLines(gc, points, npts);
  
  // TODO: Should the path be automatically closed, or is that the caller's responsability?
  // Explicitly close the path, so it is closed for stroking AND filling (implicit close = filling only)
  CGContextClosePath( gc );
  CGContextDrawPath( gc, kCGPathFillStroke );
  
  // Deallocate memory.
  delete points;
  points = NULL;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::RectangleDraw(PRectangle rc, ColourAllocated fore, ColourAllocated back)
{
  if (gc)
  {
    CGContextBeginPath( gc );
    FillColour(back);
    PenColour(fore);
    
    // Quartz integer -> float point conversion fun (see comment in SurfaceImpl::LineTo)
    // We subtract 1 from the Width() and Height() so that all our drawing is within the area defined
    // by the PRectangle. Otherwise, we draw one pixel too far to the right and bottom.
    // TODO: Create some version of PRectangleToCGRect to do this conversion for us?
    CGContextAddRect( gc, CGRectMake( rc.left + 0.5, rc.top + 0.5, rc.Width() - 1, rc.Height() - 1 ) );
    CGContextDrawPath( gc, kCGPathFillStroke );
  }
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::FillRectangle(PRectangle rc, ColourAllocated back)
{
  if (gc)
  {
    FillColour(back);
    CGRect rect = PRectangleToCGRect(rc);
    CGContextFillRect(gc, rect);
  }
}

//--------------------------------------------------------------------------------------------------

void drawImageRefCallback(CGImageRef pattern, CGContextRef gc)
{
  CGContextDrawImage(gc, CGRectMake(0, 0, CGImageGetWidth(pattern), CGImageGetHeight(pattern)), pattern);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern)
{
  SurfaceImpl& patternSurface = static_cast<SurfaceImpl &>(surfacePattern);
  
  // For now, assume that copy can only be called on PixMap surfaces. Shows up black.
  CGImageRef image = patternSurface.GetImage();
  if (image == NULL)
  {
    FillRectangle(rc, ColourAllocated(0));
    return;
  }
  
  const CGPatternCallbacks drawImageCallbacks = { 0, 
    reinterpret_cast<CGPatternDrawPatternCallback>(drawImageRefCallback), NULL };
  
  CGPatternRef pattern = CGPatternCreate(image,
                                         CGRectMake(0, 0, patternSurface.bitmapWidth, patternSurface.bitmapHeight),
                                         CGAffineTransformIdentity,
                                         patternSurface.bitmapWidth,
                                         patternSurface.bitmapHeight,
                                         kCGPatternTilingNoDistortion,
                                         true,
                                         &drawImageCallbacks
                                        );
  if (pattern != NULL)
  {
    // Create a pattern color space
    CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern( NULL );
    if( colorSpace != NULL ) {
      
      CGContextSaveGState( gc );
      CGContextSetFillColorSpace( gc, colorSpace );
      
      // Unlike the documentation, you MUST pass in a "components" parameter:
      // For coloured patterns it is the alpha value.
      const float alpha = 1.0;
      CGContextSetFillPattern( gc, pattern, &alpha );
      CGContextFillRect( gc, PRectangleToCGRect( rc ) );
      CGContextRestoreGState( gc );
      // Free the color space, the pattern and image
      CGColorSpaceRelease( colorSpace );
    } /* colorSpace != NULL */
    colorSpace = NULL;
    CGPatternRelease( pattern );
    pattern = NULL;
    CGImageRelease( image );
    image = NULL;
  } /* pattern != NULL */
}

void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
  // TODO: Look at the Win32 API to determine what this is supposed to do:
  //  ::RoundRect(hdc, rc.left + 1, rc.top, rc.right - 1, rc.bottom, 8, 8 );
  
  // Create a rectangle with semicircles at the corners
  const int MAX_RADIUS = 4;
  int radius = Platform::Minimum( MAX_RADIUS, rc.Height()/2 );
  radius = Platform::Minimum( radius, rc.Width()/2 );
  
  // Points go clockwise, starting from just below the top left
  // Corners are kept together, so we can easily create arcs to connect them
  CGPoint corners[4][3] =
  {
    {
      { rc.left, rc.top + radius },
      { rc.left, rc.top },
      { rc.left + radius, rc.top },
    },
    {
      { rc.right - radius - 1, rc.top },
      { rc.right - 1, rc.top },
      { rc.right - 1, rc.top + radius },
    },
    {
      { rc.right - 1, rc.bottom - radius - 1 },
      { rc.right - 1, rc.bottom - 1 },
      { rc.right - radius - 1, rc.bottom - 1 },
    },
    {
      { rc.left + radius, rc.bottom - 1 },
      { rc.left, rc.bottom - 1 },
      { rc.left, rc.bottom - radius - 1 },
    },
  };
  
  // Align the points in the middle of the pixels
  // TODO: Should I include these +0.5 in the array creation code above?
  for( int i = 0; i < 4*3; ++ i )
  {
    CGPoint* c = (CGPoint*) corners;
    c[i].x += 0.5;
    c[i].y += 0.5;
  }
  
  PenColour( fore );
  FillColour( back );
  
  // Move to the last point to begin the path
  CGContextBeginPath( gc );
  CGContextMoveToPoint( gc, corners[3][2].x, corners[3][2].y );
  
  for ( int i = 0; i < 4; ++ i )
  {
    CGContextAddLineToPoint( gc, corners[i][0].x, corners[i][0].y );
    CGContextAddArcToPoint( gc, corners[i][1].x, corners[i][1].y, corners[i][2].x, corners[i][2].y, radius );
  }
  
  // Close the path to enclose it for stroking and for filling, then draw it
  CGContextClosePath( gc );
  CGContextDrawPath( gc, kCGPathFillStroke );
}

void Scintilla::SurfaceImpl::AlphaRectangle(PRectangle rc, int /*cornerSize*/, ColourAllocated fill, int alphaFill,
                                            ColourAllocated /*outline*/, int /*alphaOutline*/, int /*flags*/)
{
  if ( gc ) {
    ColourDesired colour( fill.AsLong() );
    
    // Set the Fill color to match
    CGContextSetRGBFillColor( gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, colour.GetBlue() / 255.0, alphaFill / 255.0 );
    CGRect rect = PRectangleToCGRect( rc );
    CGContextFillRect( gc, rect );
  }
}

void SurfaceImpl::Ellipse(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
  // Drawing an ellipse with bezier curves. Code modified from:
  // http://www.codeguru.com/gdi/ellipse.shtml
  // MAGICAL CONSTANT to map ellipse to beziers 2/3*(sqrt(2)-1)
  const double EToBConst = 0.2761423749154;
  
  CGSize offset = CGSizeMake((int)(rc.Width() * EToBConst), (int)(rc.Height() * EToBConst));
  CGPoint centre = CGPointMake((rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2);
  
  // The control point array
  CGPoint cCtlPt[13];
  
  // Assign values to all the control points
  cCtlPt[0].x  =
  cCtlPt[1].x  =
  cCtlPt[11].x =
  cCtlPt[12].x = rc.left + 0.5;
  cCtlPt[5].x  =
  cCtlPt[6].x  =
  cCtlPt[7].x  = rc.right - 0.5;
  cCtlPt[2].x  =
  cCtlPt[10].x = centre.x - offset.width + 0.5;
  cCtlPt[4].x  =
  cCtlPt[8].x  = centre.x + offset.width + 0.5;
  cCtlPt[3].x  =
  cCtlPt[9].x  = centre.x + 0.5;
  
  cCtlPt[2].y  =
  cCtlPt[3].y  =
  cCtlPt[4].y  = rc.top + 0.5;
  cCtlPt[8].y  =
  cCtlPt[9].y  =
  cCtlPt[10].y = rc.bottom - 0.5;
  cCtlPt[7].y  =
  cCtlPt[11].y = centre.y + offset.height + 0.5;
  cCtlPt[1].y =
  cCtlPt[5].y  = centre.y - offset.height + 0.5;
  cCtlPt[0].y =
  cCtlPt[12].y =
  cCtlPt[6].y  = centre.y + 0.5;
  
  FillColour(back);
  PenColour(fore);
  
  CGContextBeginPath( gc );
  CGContextMoveToPoint( gc, cCtlPt[0].x, cCtlPt[0].y );
  
  for ( int i = 1; i < 13; i += 3 )
  {
    CGContextAddCurveToPoint( gc, cCtlPt[i].x, cCtlPt[i].y, cCtlPt[i+1].x, cCtlPt[i+1].y, cCtlPt[i+2].x, cCtlPt[i+2].y );
  }
  
  // Close the path to enclose it for stroking and for filling, then draw it
  CGContextClosePath( gc );
  CGContextDrawPath( gc, kCGPathFillStroke );
}

void SurfaceImpl::CopyImageRectangle(Surface &surfaceSource, PRectangle srcRect, PRectangle dstRect)
{
  SurfaceImpl& source = static_cast<SurfaceImpl &>(surfaceSource);
  CGImageRef image = source.GetImage();
  
  CGRect src = PRectangleToCGRect(srcRect);
  CGRect dst = PRectangleToCGRect(dstRect);
  
  /* source from QuickDrawToQuartz2D.pdf on developer.apple.com */
  float w = (float) CGImageGetWidth(image);
  float h = (float) CGImageGetHeight(image);
  CGRect drawRect = CGRectMake (0, 0, w, h);
  if (!CGRectEqualToRect (src, dst))
  {
    float sx = CGRectGetWidth(dst) / CGRectGetWidth(src);
    float sy = CGRectGetHeight(dst) / CGRectGetHeight(src);
    float dx = CGRectGetMinX(dst) - (CGRectGetMinX(src) * sx);
    float dy = CGRectGetMinY(dst) - (CGRectGetMinY(src) * sy);
    drawRect = CGRectMake (dx, dy, w*sx, h*sy);
  }
  CGContextSaveGState (gc);
  CGContextClipToRect (gc, dst);
  CGContextDrawImage (gc, drawRect, image);
  CGContextRestoreGState (gc);
}

void SurfaceImpl::Copy(PRectangle rc, Scintilla::Point from, Surface &surfaceSource) {
  // Maybe we have to make the Surface two contexts:
  // a bitmap context which we do all the drawing on, and then a "real" context
  // which we copy the output to when we call "Synchronize". Ugh! Gross and slow!
  
  // For now, assume that copy can only be called on PixMap surfaces
  SurfaceImpl& source = static_cast<SurfaceImpl &>(surfaceSource);
  
  // Get the CGImageRef
  CGImageRef image = source.GetImage();
  // If we could not get an image reference, fill the rectangle black
  if ( image == NULL )
  {
    FillRectangle( rc, ColourAllocated( 0 ) );
    return;
  }
  
  // Now draw the image on the surface
  
  // Some fancy clipping work is required here: draw only inside of rc
  CGContextSaveGState( gc );
  CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
  
  //Platform::DebugPrintf(stderr, "Copy: CGContextDrawImage: (%d, %d) - (%d X %d)\n", rc.left - from.x, rc.top - from.y, source.bitmapWidth, source.bitmapHeight );
  CGContextDrawImage( gc, CGRectMake( rc.left - from.x, rc.top - from.y, source.bitmapWidth, source.bitmapHeight ), image );
  
  // Undo the clipping fun
  CGContextRestoreGState( gc );
  
  // Done with the image
  CGImageRelease( image );
  image = NULL;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, int ybase, const char *s, int len,
                                 ColourAllocated fore, ColourAllocated back)
{
  FillRectangle(rc, back);
  DrawTextTransparent(rc, font_, ybase, s, len, fore);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, int ybase, const char *s, int len,
                                  ColourAllocated fore, ColourAllocated back)
{
  CGContextSaveGState(gc);
  CGContextClipToRect(gc, PRectangleToCGRect(rc));
  DrawTextNoClip(rc, font_, ybase, s, len, fore, back);
  CGContextRestoreGState(gc);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, int ybase, const char *s, int len, 
                                      ColourAllocated fore)
{
  textLayout->setText (reinterpret_cast<const UInt8*>(s), len, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
  
  // The Quartz RGB fill color influences the ATSUI color
  FillColour(fore);
  
  // Draw text vertically flipped as OS X uses a coordinate system where +Y is upwards.
  textLayout->draw(rc.left, ybase, true);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, int *positions)
{
  // sample at http://developer.apple.com/samplecode/ATSUICurveAccessDemo/listing1.html
  // sample includes use of ATSUGetGlyphInfo which would be better for older
  // OSX systems.  We should expand to using that on older systems as well.
  for (int i = 0; i < len; i++)
    positions [i] = 0;
  
  // We need the right X coords, so we have to append a char to get the left coord of thast extra char
  char* buf = (char*) malloc (len+1);
  if (!buf)
    return;
  
  memcpy (buf, s, len);
  buf [len] = '.';
  
  textLayout->setText (reinterpret_cast<const UInt8*>(buf), len+1, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
  ATSUGlyphInfoArray* theGlyphInfoArrayPtr;
  ByteCount theArraySize;
  
  // Get the GlyphInfoArray
  ATSUTextLayout layout = textLayout->getLayout();
  if ( noErr == ATSUGetGlyphInfo (layout, 0, textLayout->getLength(), &theArraySize, NULL))
  {
    theGlyphInfoArrayPtr = (ATSUGlyphInfoArray *) malloc (theArraySize + sizeof(ItemCount) + sizeof(ATSUTextLayout));
    if (theGlyphInfoArrayPtr)
    {
      if (noErr == ATSUGetGlyphInfo (layout, 0, textLayout->getLength(), &theArraySize, theGlyphInfoArrayPtr))
      {
        // do not count the first item, which is at the beginning of the line
        for ( UniCharCount unicodePosition = 1, i = 0; i < len && unicodePosition < theGlyphInfoArrayPtr->numGlyphs; unicodePosition ++ )
        {
          // The ideal position is the x coordinate of the glyph, relative to the beginning of the line
          int position = (int)( theGlyphInfoArrayPtr->glyphs[unicodePosition].idealX + 0.5 );    // These older APIs return float values
          unsigned char uch = s[i];
          positions[i++] = position;
          
          // If we are using unicode (UTF8), map the Unicode position back to the UTF8 characters,
          // as 1 unicode character can map to multiple UTF8 characters.
          // See: http://www.tbray.org/ongoing/When/200x/2003/04/26/UTF
          // Or: http://www.cl.cam.ac.uk/~mgk25/unicode.html
          if ( unicodeMode ) 
          {
            unsigned char mask = 0xc0;
            int count = 1;
            // Add one additonal byte for each extra high order one in the byte
            while ( uch >= mask && count < 8 ) 
            {
              positions[i++] = position;
              count ++;
              mask = mask >> 1 | 0x80; // add an additional one in the highest order position
            }
          }
        }
      }
      
      // Free the GlyphInfoArray
      free (theGlyphInfoArrayPtr);
    }
  }
  free (buf);
}

int SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
  if (font_.GetID())
  {
    textLayout->setText (reinterpret_cast<const UInt8*>(s), len, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
    
    // TODO: Maybe I should add some sort of text measurement features to QuartzTextLayout?
    unsigned long actualNumberOfBounds = 0;
    ATSTrapezoid glyphBounds;
    
    // We get a single bound, since the text should only require one. If it requires more, there is an issue
    if ( ATSUGetGlyphBounds( textLayout->getLayout(), 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 1, &glyphBounds, &actualNumberOfBounds ) != noErr || actualNumberOfBounds != 1 )
    {
      Platform::DebugDisplay( "ATSUGetGlyphBounds failed in WidthText" );
      return 0;
    }
    
    //Platform::DebugPrintf( "WidthText: \"%*s\" = %ld\n", len, s, Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x ) );
    return Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x );
  }
  return 1;
}

int SurfaceImpl::WidthChar(Font &font_, char ch) {
  char str[2] = { ch, '\0' };
  if (font_.GetID())
  {
    textLayout->setText (reinterpret_cast<const UInt8*>(str), 1, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
    
    // TODO: Maybe I should add some sort of text measurement features to QuartzTextLayout?
    unsigned long actualNumberOfBounds = 0;
    ATSTrapezoid glyphBounds;
    
    // We get a single bound, since the text should only require one. If it requires more, there is an issue
    if ( ATSUGetGlyphBounds( textLayout->getLayout(), 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 1, &glyphBounds, &actualNumberOfBounds ) != noErr || actualNumberOfBounds != 1 )
    {
      Platform::DebugDisplay( "ATSUGetGlyphBounds failed in WidthChar" );
      return 0;
    }
    
    return Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x );
  }
  else
    return 1;
}

// Three possible strategies for determining ascent and descent of font:
// 1) Call ATSUGetGlyphBounds with string containing all letters, numbers and punctuation.
// 2) Use the ascent and descent fields of the font.
// 3) Call ATSUGetGlyphBounds with string as 1 but also including accented capitals.
// Smallest values given by 1 and largest by 3 with 2 in between.
// Techniques 1 and 2 sometimes chop off extreme portions of ascenders and
// descenders but are mostly OK except for accented characters which are
// rarely used in code.

// This string contains a good range of characters to test for size.
const char sizeString[] = "`~!@#$%^&*()-_=+\\|[]{};:\"\'<,>.?/1234567890"
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

int SurfaceImpl::Ascent(Font &font_) {
  if (!font_.GetID())
    return 1;
  
  ATSUTextMeasurement ascent = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAttribute<ATSUTextMeasurement>( kATSUAscentTag );
  return Fix2Long( ascent );
}

int SurfaceImpl::Descent(Font &font_) {
  if (!font_.GetID())
    return 1;
  
  ATSUTextMeasurement descent = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAttribute<ATSUTextMeasurement>( kATSUDescentTag );
  return Fix2Long( descent );
}

int SurfaceImpl::InternalLeading(Font &) {
  // TODO: How do we get EM_Size?
  // internal leading = ascent - descent - EM_size
  return 0;
}

int SurfaceImpl::ExternalLeading(Font &font_) {
  if (!font_.GetID())
    return 1;
  
  ATSUTextMeasurement lineGap = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAttribute<ATSUTextMeasurement>( kATSULeadingTag );
  return Fix2Long( lineGap );
}

int SurfaceImpl::Height(Font &font_) {
  return Ascent(font_) + Descent(font_);
}

int SurfaceImpl::AverageCharWidth(Font &font_) {
  
  if (!font_.GetID())
    return 1;
  
  const int sizeStringLength = (sizeof( sizeString ) / sizeof( sizeString[0] ) - 1);
  int width = WidthText( font_, sizeString, sizeStringLength  );
  
  return (int) ((width / (float) sizeStringLength) + 0.5);
  
  /*
   ATSUStyle textStyle = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getATSUStyle();
   ATSUFontID fontID;
   
   ByteCount actualSize = 0;
   if ( ATSUGetAttribute( textStyle, kATSUFontTag, sizeof( fontID ), &fontID, &actualSize ) != noErr )
   {
   Platform::DebugDisplay( "ATSUGetAttribute failed" );
   return 1;
   }
   
   ATSFontMetrics metrics;
   memset( &metrics, 0, sizeof( metrics ) );
   if ( ATSFontGetHorizontalMetrics( fontID, kATSOptionFlagsDefault, &metrics ) != noErr )
   {
   Platform::DebugDisplay( "ATSFontGetHorizontalMetrics failed in AverageCharWidth" );
   return 1;
   }
   
   printf( "%f %f %f\n", metrics.avgAdvanceWidth * 32, metrics.ascent * 32, metrics.descent * 32 );
   
   return (int) (metrics.avgAdvanceWidth + 0.5);*/
}

int SurfaceImpl::SetPalette(Scintilla::Palette *, bool) {
  // Mac OS X is always true colour (I think) so this doesn't matter
  return 0;
}

void SurfaceImpl::SetClip(PRectangle rc) {
  CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
}

void SurfaceImpl::FlushCachedState() {
  CGContextSynchronize( gc );
}

void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
  unicodeMode = unicodeMode_;
}

void SurfaceImpl::SetDBCSMode(int codePage) {
  // TODO: Implement this for code pages != UTF-8
}

Surface *Surface::Allocate()
{
  return new SurfaceImpl();
}

//----------------- Window -------------------------------------------------------------------------

Window::~Window() {
}

//--------------------------------------------------------------------------------------------------

void Window::Destroy()
{
  if (windowRef)
  {
    // not used
  }
  wid = 0;
}

//--------------------------------------------------------------------------------------------------

bool Window::HasFocus()
{
  NSView* container = reinterpret_cast<NSView*>(wid);
  return [[container window] firstResponder] == container;
}

//--------------------------------------------------------------------------------------------------

PRectangle Window::GetPosition()
{
  NSRect rect= [reinterpret_cast<NSView*>(wid) frame];
  
  return PRectangle(NSMinX(rect), NSMinY(rect), NSMaxX(rect), NSMaxY(rect));
}

//--------------------------------------------------------------------------------------------------

void Window::SetPosition(PRectangle rc)
{
  // Moves this view inside the parent view
  if ( wid )
  {
    // Set the frame on the view, the function handles the rest
    //        CGRect r = PRectangleToCGRect( rc );
    //        HIViewSetFrame( reinterpret_cast<HIViewRef>( wid ), &r );
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetPositionRelative(PRectangle rc, Window window) {
  //    // used to actually move child windows (ie. listbox/calltip) so we have to move
  //    // the window, not the hiview
  //    if (windowRef) {
  //        // we go through some contortions here to get an accurate location for our
  //        // child windows.  This is necessary due to the multiple ways an embedding
  //        // app may be setup.  See SciTest/main.c (GOOD && BAD) for test case.
  //        WindowRef relativeWindow = GetControlOwner(reinterpret_cast<HIViewRef>( window.GetID() ));
  //        WindowRef thisWindow = reinterpret_cast<WindowRef>( windowRef );
  //
  //        Rect portBounds;
  //        ::GetWindowBounds(relativeWindow, kWindowStructureRgn, &portBounds);
  //        //fprintf(stderr, "portBounds %d %d %d %d\n", portBounds.left, portBounds.top, portBounds.right, portBounds.bottom);
  //        PRectangle hbounds = window.GetPosition();
  //        //fprintf(stderr, "hbounds %d %d %d %d\n", hbounds.left, hbounds.top, hbounds.right, hbounds.bottom);
  //        HIViewRef parent = HIViewGetSuperview(reinterpret_cast<HIViewRef>( window.GetID() ));
  //        Rect pbounds;
  //        GetControlBounds(parent, &pbounds);
  //        //fprintf(stderr, "pbounds %d %d %d %d\n", pbounds.left, pbounds.top, pbounds.right, pbounds.bottom);
  //
  //        PRectangle bounds;
  //        bounds.top = portBounds.top + pbounds.top + hbounds.top + rc.top;
  //        bounds.bottom = bounds.top + rc.Height();
  //        bounds.left = portBounds.left + pbounds.left + hbounds.left + rc.left;
  //        bounds.right = bounds.left + rc.Width();
  //        //fprintf(stderr, "bounds %d %d %d %d\n", bounds.left, bounds.top, bounds.right, bounds.bottom);
  //
  //        MoveWindow(thisWindow, bounds.left, bounds.top, false);
  //        SizeWindow(thisWindow, bounds.Width(), bounds.Height(), true);
  //
  //        SetPosition(PRectangle(0,0,rc.Width(),rc.Height()));
  //    } else {
  //        SetPosition(rc);
  //    }
}

//--------------------------------------------------------------------------------------------------

PRectangle Window::GetClientPosition()
{
  // This means, in MacOS X terms, get the "frame bounds". Call GetPosition, just like on Win32.
  return GetPosition();
}

//--------------------------------------------------------------------------------------------------

void Window::Show(bool show)
{
  //    if ( wid ) {
  //        HIViewSetVisible( reinterpret_cast<HIViewRef>( wid ), show );
  //    }
  //    // this is necessary for calltip/listbox
  //    if (windowRef) {
  //        WindowRef thisWindow = reinterpret_cast<WindowRef>( windowRef );
  //        if (show) {
  //            ShowWindow( thisWindow );
  //            DrawControls( thisWindow );
  //        } else
  //            HideWindow( thisWindow );
  //    }
}

//--------------------------------------------------------------------------------------------------

/**
 * Invalidates the entire window (here an NSView) so it is completely redrawn.
 */
void Window::InvalidateAll()
{
  if (wid)
  {
    NSView* container = reinterpret_cast<NSView*>(wid);
    container.needsDisplay = YES;
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Invalidates part of the window (here an NSView) so only this part redrawn.
 */
void Window::InvalidateRectangle(PRectangle rc)
{
  if (wid)
  {
    NSView* container = reinterpret_cast<NSView*>(wid);
    [container setNeedsDisplayInRect: PRectangleToNSRect(rc)];
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetFont(Font &)
{
  // TODO: Do I need to implement this? MSDN: specifies the font that a control is to use when drawing text.
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts the Scintilla cursor enum into an NSCursor and stores it in the associated NSView,
 * which then will take care to set up a new mouse tracking rectangle.
 */
void Window::SetCursor(Cursor curs)
{
  if (wid)
  {
    InnerView* container = reinterpret_cast<InnerView*>(wid);
    [container setCursor: curs];
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetTitle(const char *s)
{
  //    WindowRef window = GetControlOwner(reinterpret_cast<HIViewRef>( wid ));
  //    CFStringRef title = CFStringCreateWithCString(kCFAllocatorDefault, s, kCFStringEncodingMacRoman);
  //    SetWindowTitleWithCFString(window, title);
  //    CFRelease(title);
}

//--------------------------------------------------------------------------------------------------

PRectangle Window::GetMonitorRect(Point)
{
	return PRectangle();
}

//----------------- ListBox ------------------------------------------------------------------------

ListBox::ListBox()
{
}

//--------------------------------------------------------------------------------------------------

ListBox::~ListBox()
{
}

//--------------------------------------------------------------------------------------------------

static const OSType scintillaListBoxType = 'sclb';

enum {
  kItemsPerContainer = 1,
  kIconColumn = 'icon',
  kTextColumn = 'text'
};
static SInt32 kScrollBarWidth = 0;

class LineData {
  int *types;
  CFStringRef *strings;
  int len;
  int maximum;
public:
  LineData() :types(0), strings(0), len(0), maximum(0) {}
  ~LineData() {
    Clear();
  }
  void Clear() {
    delete []types;
    types = 0;
    for (int i=0; i<maximum; i++) {
      if (strings[i]) CFRelease(strings[i]);
    }
    delete []strings;
    strings = 0;
    len = 0;
    maximum = 0;
  }
  void Add(int index, int type, CFStringRef str ) {
    if (index >= maximum) {
      if (index >= len) {
        int lenNew = (index+1) * 2;
        int *typesNew = new int[lenNew];
        CFStringRef *stringsNew = new CFStringRef[lenNew];
        for (int i=0; i<maximum; i++) {
          typesNew[i] = types[i];
          stringsNew[i] = strings[i];
        }
        delete []types;
        delete []strings;
        types = typesNew;
        strings = stringsNew;
        len = lenNew;
      }
      while (maximum < index) {
        types[maximum] = 0;
        strings[maximum] = 0;
        maximum++;
      }
    }
    types[index] = type;
    strings[index] = str;
    if (index == maximum) {
      maximum++;
    }
  }
  int GetType(int index) {
    if (index < maximum) {
      return types[index];
    } else {
      return 0;
    }
  }
  CFStringRef GetString(int index) {
    if (index < maximum) {
      return strings[index];
    } else {
      return 0;
    }
  }
};

//----------------- ListBoxImpl --------------------------------------------------------------------

class ListBoxImpl : public ListBox
{
private:
  ControlRef lb;
  XPMSet xset;
  int lineHeight;
  bool unicodeMode;
  int desiredVisibleRows;
  unsigned int maxItemWidth;
  unsigned int aveCharWidth;
  Font font;
  int maxWidth;
  
  void InstallDataBrowserCustomCallbacks();
  void ConfigureDataBrowser();
  
  static pascal OSStatus  WindowEventHandler(EventHandlerCallRef  inCallRef,
                                             EventRef inEvent,
                                             void *inUserData );
  EventHandlerRef eventHandler;
  
protected:
  WindowRef windowRef;
  
public:
  LineData ld;
  CallBackAction doubleClickAction;
  void *doubleClickActionData;
  
  ListBoxImpl() : lb(NULL), lineHeight(10), unicodeMode(false),
  desiredVisibleRows(5), maxItemWidth(0), aveCharWidth(8),
  doubleClickAction(NULL), doubleClickActionData(NULL)
  {
    if (kScrollBarWidth == 0)
      ;//GetThemeMetric(kThemeMetricScrollBarWidth, &kScrollBarWidth);
  }
  
  ~ListBoxImpl() {};
  void SetFont(Font &font);
  void Create(Window &parent, int ctrlID, Scintilla::Point pt, int lineHeight_, bool unicodeMode_);
  void SetAverageCharWidth(int width);
  void SetVisibleRows(int rows);
  int GetVisibleRows() const;
  PRectangle GetDesiredRect();
  int CaretFromEdge();
  void Clear();
  void Append(char *s, int type = -1);
  int Length();
  void Select(int n);
  int GetSelection();
  int Find(const char *prefix);
  void GetValue(int n, char *value, int len);
  void Sort();
  void RegisterImage(int type, const char *xpm_data);
  void ClearRegisteredImages();
  void SetDoubleClickAction(CallBackAction action, void *data) {
    doubleClickAction = action;
    doubleClickActionData = data;
  }
  
  int IconWidth();
  void ShowHideScrollbar();
#ifdef DB_TABLE_ROW_HEIGHT
  void SetRowHeight(DataBrowserItemID itemID);
#endif
  
  void DrawRow(DataBrowserItemID item,
               DataBrowserPropertyID property,
               DataBrowserItemState itemState,
               const Rect *theRect);
  
  void SetList(const char* list, char separator, char typesep);
};

ListBox *ListBox::Allocate() {
  ListBoxImpl *lb = new ListBoxImpl();
  return lb;
}

void ListBoxImpl::Create(Window &/*parent*/, int /*ctrlID*/, Scintilla::Point /*pt*/,
                         int lineHeight_, bool unicodeMode_) {
  lineHeight = lineHeight_;
  unicodeMode = unicodeMode_;
  maxWidth = 2000;
  
  //WindowClass windowClass = kHelpWindowClass;
  //WindowAttributes attributes = kWindowNoAttributes;
  Rect contentBounds;
  WindowRef outWindow;
  
  contentBounds.top = contentBounds.left = 0;
  contentBounds.right = 100;
  contentBounds.bottom = lineHeight * desiredVisibleRows;
  
  //CreateNewWindow(windowClass, attributes, &contentBounds, &outWindow);
  
  //InstallStandardEventHandler(GetWindowEventTarget(outWindow));
  
  //ControlRef root;
  //CreateRootControl(outWindow, &root);
  
  //CreateDataBrowserControl(outWindow, &contentBounds, kDataBrowserListView, &lb);
  
#ifdef DB_TABLE_ROW_HEIGHT
  // TODO: does not seem to have any effect
  //SetDataBrowserTableViewRowHeight(lb, lineHeight);
#endif
  
  // get rid of the frame, forces databrowser to the full size
  // of the window
  //Boolean frameAndFocus = false;
  //SetControlData(lb, kControlNoPart, kControlDataBrowserIncludesFrameAndFocusTag,
  //       sizeof(frameAndFocus), &frameAndFocus);
  
  //ListBoxImpl* lbThis = this;
  //SetControlProperty( lb, scintillaListBoxType, 0, sizeof( this ), &lbThis );
  
  ConfigureDataBrowser();
  InstallDataBrowserCustomCallbacks();
  
  // install event handlers
  /*
  static const EventTypeSpec kWindowEvents[] =
  {
    { kEventClassMouse, kEventMouseDown },
    { kEventClassMouse, kEventMouseMoved },
  };
   */
  
  eventHandler = NULL;
  //InstallWindowEventHandler( outWindow, WindowEventHandler,
  //               GetEventTypeCount( kWindowEvents ),
  //               kWindowEvents, this, &eventHandler );
  
  wid = lb;
  //SetControlVisibility(lb, true, true);
  SetControl(lb);
  SetWindow(outWindow);
}

pascal OSStatus ListBoxImpl::WindowEventHandler(
                                                EventHandlerCallRef inCallRef,
                                                EventRef            inEvent,
                                                void*               inUserData )
{
  
  switch (kEventClassMouse /* GetEventClass(inEvent) */) {
    case kEventClassMouse:
      switch (kEventMouseDown /* GetEventKind(inEvent) */ )
    {
      case kEventMouseMoved:
      {
        //SetThemeCursor( kThemeArrowCursor );
        break;
      }
      case kEventMouseDown:
      {
        // we cannot handle the double click from the databrowser notify callback as
        // calling doubleClickAction causes the listbox to be destroyed.  It is
        // safe to do it from this event handler since the destroy event will be queued
        // until we're done here.
        /*
         TCarbonEvent        event( inEvent );
         EventMouseButton inMouseButton;
         event.GetParameter<EventMouseButton>( kEventParamMouseButton, typeMouseButton, &inMouseButton );
         
         UInt32 inClickCount;
         event.GetParameter( kEventParamClickCount, &inClickCount );
         if (inMouseButton == kEventMouseButtonPrimary && inClickCount == 2) {
         // handle our single mouse click now
         ListBoxImpl* listbox = reinterpret_cast<ListBoxImpl*>( inUserData );
         const WindowRef window = GetControlOwner(listbox->lb);
         const HIViewRef rootView = HIViewGetRoot( window );
         HIViewRef targetView = NULL;
         HIViewGetViewForMouseEvent( rootView, inEvent, &targetView );
         if ( targetView == listbox->lb )
         {
         if (listbox->doubleClickAction != NULL) {
         listbox->doubleClickAction(listbox->doubleClickActionData);
         }
         }
         }
         */
      }
    }
  }
  return eventNotHandledErr;
}

#ifdef DB_TABLE_ROW_HEIGHT
void ListBoxImpl::SetRowHeight(DataBrowserItemID itemID)
{
  // XXX does not seem to have any effect
  //SetDataBrowserTableViewItemRowHeight(lb, itemID, lineHeight);
}
#endif

void ListBoxImpl::DrawRow(DataBrowserItemID item,
                          DataBrowserPropertyID property,
                          DataBrowserItemState itemState,
                          const Rect *theRect)
{
  Rect row = *theRect;
  row.left = 0;
  
  ColourPair fore;
  
  if (itemState == kDataBrowserItemIsSelected) {
    long        systemVersion;
    Gestalt( gestaltSystemVersion, &systemVersion );
    //  Panther DB starts using kThemeBrushSecondaryHighlightColor for inactive browser hilighting
    if ( (systemVersion >= 0x00001030) )//&& (IsControlActive( lb ) == false) )
      ;//SetThemePen( kThemeBrushSecondaryHighlightColor, 32, true );
    else
      ; //SetThemePen( kThemeBrushAlternatePrimaryHighlightColor, 32, true );
    
    PaintRect(&row);
    fore = ColourDesired(0xff,0xff,0xff);
  }
  
  int widthPix = xset.GetWidth() + 2;
  int pixId = ld.GetType(item - 1);
  XPM *pxpm = xset.Get(pixId);
  
  char s[255];
  GetValue(item - 1, s, 255);
  
  Surface *surfaceItem = Surface::Allocate();
  if (surfaceItem) {
    CGContextRef    cgContext;
    GrafPtr        port;
    Rect bounds;
    
    //GetControlBounds(lb, &bounds);
    GetPort( &port );
    QDBeginCGContext( port, &cgContext );
    
    CGContextSaveGState( cgContext );
    CGContextTranslateCTM(cgContext, 0, bounds.bottom - bounds.top);
    CGContextScaleCTM(cgContext, 1.0, -1.0);
    
    surfaceItem->Init(cgContext, NULL);
    
    int left = row.left;
    if (pxpm) {
      PRectangle rc(left + 1, row.top,
                    left + 1 + widthPix, row.bottom);
      pxpm->Draw(surfaceItem, rc);
    }
    
    // draw the text
    PRectangle trc(left + 2 + widthPix, row.top, row.right, row.bottom);
    int ascent = surfaceItem->Ascent(font) - surfaceItem->InternalLeading(font);
    int ytext = trc.top + ascent + 1;
    trc.bottom = ytext + surfaceItem->Descent(font) + 1;
    surfaceItem->DrawTextTransparent( trc, font, ytext, s, strlen(s), fore.allocated );
    
    CGContextRestoreGState( cgContext );
    QDEndCGContext( port, &cgContext );
    delete surfaceItem;
  }
}


pascal void ListBoxDrawItemCallback(ControlRef browser, DataBrowserItemID item,
                                    DataBrowserPropertyID property,
                                    DataBrowserItemState itemState,
                                    const Rect *theRect, SInt16 gdDepth,
                                    Boolean colorDevice)
{
  if (property != kIconColumn) return;
  ListBoxImpl* lbThis = NULL;
  //OSStatus err;
  //err = GetControlProperty( browser, scintillaListBoxType, 0, sizeof( lbThis ), NULL, &lbThis );
  // adjust our rect
  lbThis->DrawRow(item, property, itemState, theRect);
  
}

void ListBoxImpl::ConfigureDataBrowser()
{
  DataBrowserViewStyle viewStyle;
  //DataBrowserSelectionFlags selectionFlags;
  //GetDataBrowserViewStyle(lb, &viewStyle);
  
  //SetDataBrowserHasScrollBars(lb, false, true);
  //SetDataBrowserListViewHeaderBtnHeight(lb, 0);
  //GetDataBrowserSelectionFlags(lb, &selectionFlags);
  //SetDataBrowserSelectionFlags(lb, selectionFlags |= kDataBrowserSelectOnlyOne);
  // if you change the hilite style, also change the style in ListBoxDrawItemCallback
  //SetDataBrowserTableViewHiliteStyle(lb, kDataBrowserTableViewFillHilite);
  
  Rect insetRect;
  //GetDataBrowserScrollBarInset(lb, &insetRect);
  
  insetRect.right = kScrollBarWidth - 1;
  //SetDataBrowserScrollBarInset(lb, &insetRect);
  
  switch (viewStyle)
  {
    case kDataBrowserListView:
    {
      DataBrowserListViewColumnDesc iconCol;
      iconCol.headerBtnDesc.version = kDataBrowserListViewLatestHeaderDesc;
      iconCol.headerBtnDesc.minimumWidth = 0;
      iconCol.headerBtnDesc.maximumWidth = maxWidth;
      iconCol.headerBtnDesc.titleOffset = 0;
      iconCol.headerBtnDesc.titleString = NULL;
      iconCol.headerBtnDesc.initialOrder = kDataBrowserOrderIncreasing;
      
      iconCol.headerBtnDesc.btnFontStyle.flags = kControlUseJustMask;
      iconCol.headerBtnDesc.btnFontStyle.just = teFlushLeft;
      
      iconCol.headerBtnDesc.btnContentInfo.contentType = kControlContentTextOnly;
      
      iconCol.propertyDesc.propertyID = kIconColumn;
      iconCol.propertyDesc.propertyType = kDataBrowserCustomType;
      iconCol.propertyDesc.propertyFlags = kDataBrowserListViewSelectionColumn;
      
      //AddDataBrowserListViewColumn(lb, &iconCol, kDataBrowserListViewAppendColumn);
    }  break;
      
  }
}

void ListBoxImpl::InstallDataBrowserCustomCallbacks()
{
  /*
   DataBrowserCustomCallbacks callbacks;
   
   callbacks.version = kDataBrowserLatestCustomCallbacks;
   verify_noerr(InitDataBrowserCustomCallbacks(&callbacks));
   callbacks.u.v1.drawItemCallback = NewDataBrowserDrawItemUPP(ListBoxDrawItemCallback);
   callbacks.u.v1.hitTestCallback = NULL;//NewDataBrowserHitTestUPP(ListBoxHitTestCallback);
   callbacks.u.v1.trackingCallback = NULL;//NewDataBrowserTrackingUPP(ListBoxTrackingCallback);
   callbacks.u.v1.editTextCallback = NULL;
   callbacks.u.v1.dragRegionCallback = NULL;
   callbacks.u.v1.acceptDragCallback = NULL;
   callbacks.u.v1.receiveDragCallback = NULL;
   
   SetDataBrowserCustomCallbacks(lb, &callbacks);
   */
}

void ListBoxImpl::SetFont(Font &font_) {
  // Having to do this conversion is LAME
  QuartzTextStyle *ts = reinterpret_cast<QuartzTextStyle*>( font_.GetID() );
  ControlFontStyleRec style;
  ATSUAttributeValuePtr value;
  ATSUFontID        fontID;
  style.flags = kControlUseFontMask | kControlUseSizeMask | kControlAddToMetaFontMask;
  ts->getAttribute( kATSUFontTag, sizeof(fontID), &fontID, NULL );
  ATSUFontIDtoFOND(fontID, &style.font, NULL);
  ts->getAttribute( kATSUSizeTag, sizeof(Fixed), &value, NULL );
  style.size = ((SInt16)FixRound((SInt32)value));
  //SetControlFontStyle(lb, &style);
  
#ifdef DB_TABLE_ROW_HEIGHT
  //  XXX this doesn't *stick*
  ATSUTextMeasurement ascent = ts->getAttribute<ATSUTextMeasurement>( kATSUAscentTag );
  ATSUTextMeasurement descent = ts->getAttribute<ATSUTextMeasurement>( kATSUDescentTag );
  lineHeight = Fix2Long( ascent ) + Fix2Long( descent );
  //SetDataBrowserTableViewRowHeight(lb, lineHeight + lineLeading);
#endif
  
  // !@&^#%$ we cant copy Font, but we need one for our custom drawing
  Str255 fontName255;
  char fontName[256];
  FMGetFontFamilyName(style.font, fontName255);
  
  CFStringRef fontNameCF = ::CFStringCreateWithPascalString( kCFAllocatorDefault, fontName255, kCFStringEncodingMacRoman );
  ::CFStringGetCString( fontNameCF, fontName, (CFIndex)255, kCFStringEncodingMacRoman );
  
  font.Create((const char *)fontName, 0, style.size, false, false);
}

void ListBoxImpl::SetAverageCharWidth(int width) {
  aveCharWidth = width;
}

void ListBoxImpl::SetVisibleRows(int rows) {
  desiredVisibleRows = rows;
}

int ListBoxImpl::GetVisibleRows() const {
  // XXX Windows & GTK do this, but it seems incorrect to me.  Other logic
  //     to do with visible rows is essentially the same across platforms.
  return desiredVisibleRows;
  /*
   // This would be more correct
   int rows = Length();
   if ((rows == 0) || (rows > desiredVisibleRows))
   rows = desiredVisibleRows;
   return rows;
   */
}

PRectangle ListBoxImpl::GetDesiredRect() {
  PRectangle rcDesired = GetPosition();
  
  // XXX because setting the line height on the table doesnt
  //     *stick*, we'll have to suffer and just use whatever
  //     the table desides is the correct height.
  UInt16 itemHeight;// = lineHeight;
  //GetDataBrowserTableViewRowHeight(lb, &itemHeight);
  
  int rows = Length();
  if ((rows == 0) || (rows > desiredVisibleRows))
    rows = desiredVisibleRows;
  
  rcDesired.bottom = itemHeight * rows;
  rcDesired.right = rcDesired.left + maxItemWidth + aveCharWidth;
  
  if (Length() > rows) 
    rcDesired.right += kScrollBarWidth;
  rcDesired.right += IconWidth();
  
  // Set the column width
  //SetDataBrowserTableViewColumnWidth (lb, UInt16 (rcDesired.right - rcDesired.left));
  return rcDesired;
}

void ListBoxImpl::ShowHideScrollbar() {
  int rows = Length();
  if (rows > desiredVisibleRows) {
    //SetDataBrowserHasScrollBars(lb, false, true);
  } else {
    //SetDataBrowserHasScrollBars(lb, false, false);
  }
}

int ListBoxImpl::IconWidth() {
  return xset.GetWidth() + 2;
}

int ListBoxImpl::CaretFromEdge() {
  return 0;
}

void ListBoxImpl::Clear() {
  // passing NULL to "items" arg 4 clears the list
  maxItemWidth = 0;
  ld.Clear();
  //AddDataBrowserItems (lb, kDataBrowserNoItem, 0, NULL, kDataBrowserItemNoProperty);
}

void ListBoxImpl::Append(char *s, int type) {
  int count = Length();
  CFStringRef r = CFStringCreateWithCString(NULL, s, kTextEncodingMacRoman);
  ld.Add(count, type, r);
  
  Scintilla::SurfaceImpl surface;
  unsigned int width = surface.WidthText (font, s, strlen (s));
  if (width > maxItemWidth)
    maxItemWidth = width;
  
  DataBrowserItemID items[1];
  items[0] = count + 1;
  //AddDataBrowserItems (lb, kDataBrowserNoItem, 1, items, kDataBrowserItemNoProperty);
  ShowHideScrollbar();
}

void ListBoxImpl::SetList(const char* list, char separator, char typesep) {
  // XXX copied from PlatGTK, should be in base class
  Clear();
  int count = strlen(list) + 1;
  char *words = new char[count];
  if (words) {
    memcpy(words, list, count);
    char *startword = words;
    char *numword = NULL;
    int i = 0;
    for (; words[i]; i++) {
      if (words[i] == separator) {
        words[i] = '\0';
        if (numword)
          *numword = '\0';
        Append(startword, numword?atoi(numword + 1):-1);
        startword = words + i + 1;
        numword = NULL;
      } else if (words[i] == typesep) {
        numword = words + i;
      }
    }
    if (startword) {
      if (numword)
        *numword = '\0';
      Append(startword, numword?atoi(numword + 1):-1);
    }
    delete []words;
  }
}

int ListBoxImpl::Length() {
  UInt32 numItems = 0;
  //GetDataBrowserItemCount(lb, kDataBrowserNoItem, false, kDataBrowserItemAnyState, &numItems);
  return (int)numItems;
}

void ListBoxImpl::Select(int n) {
  DataBrowserItemID items[1];
  items[0] = n + 1;
  //SetDataBrowserSelectedItems(lb, 1, items, kDataBrowserItemsAssign);
  //RevealDataBrowserItem(lb, items[0], kIconColumn, kDataBrowserRevealOnly);
  // force update on selection
  //Draw1Control(lb);
}

int ListBoxImpl::GetSelection() {
  Handle selectedItems = NewHandle(0);
  //GetDataBrowserItems(lb, kDataBrowserNoItem, true, kDataBrowserItemIsSelected, selectedItems);
  UInt32 numSelectedItems = GetHandleSize(selectedItems)/sizeof(DataBrowserItemID);
  if (numSelectedItems == 0) {
    return -1;
  }
  HLock( selectedItems );
  DataBrowserItemID *individualItem = (DataBrowserItemID*)( *selectedItems );
  DataBrowserItemID selected[numSelectedItems];
  selected[0] = *individualItem;
  HUnlock( selectedItems );
  return selected[0] - 1;
}

int ListBoxImpl::Find(const char *prefix) {
  int count = Length();
  char s[255];
  for (int i = 0; i < count; i++) {
    GetValue(i, s, 255);
    if ((s[0] != '\0') && (0 == strncmp(prefix, s, strlen(prefix)))) {
      return i;
    }
  }
  return - 1;
}

void ListBoxImpl::GetValue(int n, char *value, int len) {
  CFStringRef textString = ld.GetString(n);
  if (textString == NULL) {
    value[0] = '\0';
    return;
  }
  CFIndex numUniChars = CFStringGetLength( textString );
  
  // XXX how do we know the encoding of the listbox?
  CFStringEncoding encoding = kCFStringEncodingUTF8; //( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingASCII);
  CFIndex maximumByteLength = CFStringGetMaximumSizeForEncoding( numUniChars, encoding ) + 1;
  char* text = new char[maximumByteLength];
  CFIndex usedBufferLength = 0;
  CFStringGetBytes( textString, CFRangeMake( 0, numUniChars ), encoding,
                   '?', false, reinterpret_cast<UInt8*>( text ),
                   maximumByteLength, &usedBufferLength );
  text[usedBufferLength] = '\0'; // null terminate the ASCII/UTF8 string
  
  if (text && len > 0) {
    strncpy(value, text, len);
    value[len - 1] = '\0';
  } else {
    value[0] = '\0';
  }
  delete text;
}

void ListBoxImpl::Sort() {
  // TODO: Implement this
  fprintf(stderr, "ListBox::Sort\n");
}

void ListBoxImpl::RegisterImage(int type, const char *xpm_data) {
  xset.Add(type, xpm_data);
}

void ListBoxImpl::ClearRegisteredImages() {
  xset.Clear();
}

//----------------- ScintillaContextMenu -----------------------------------------------------------

@implementation ScintillaContextMenu : NSMenu

// This NSMenu subclass serves also as target for menu commands and forwards them as
// notfication messages to the front end.

- (void) handleCommand: (NSMenuItem*) sender
{
  owner->HandleCommand([sender tag]);
}

//--------------------------------------------------------------------------------------------------

- (void) setOwner: (Scintilla::ScintillaCocoa*) newOwner
{
  owner = newOwner;
}

@end

//----------------- Menu ---------------------------------------------------------------------------

Menu::Menu()
  : mid(0)
{
}

//--------------------------------------------------------------------------------------------------

void Menu::CreatePopUp()
{
  Destroy();
  mid = [[ScintillaContextMenu alloc] initWithTitle: @""];
}

//--------------------------------------------------------------------------------------------------

void Menu::Destroy()
{
  ScintillaContextMenu* menu = reinterpret_cast<ScintillaContextMenu*>(mid);
  [menu release];
  mid = NULL;
}

//--------------------------------------------------------------------------------------------------

void Menu::Show(Point pt, Window &)
{
  // Cocoa menus are handled a bit differently. We only create the menu. The framework
  // takes care to show it properly.
}

//----------------- ElapsedTime --------------------------------------------------------------------

// TODO: Consider if I should be using GetCurrentEventTime instead of gettimeoday
ElapsedTime::ElapsedTime() {
  struct timeval curTime;
  int retVal;
  retVal = gettimeofday( &curTime, NULL );
  
  bigBit = curTime.tv_sec;
  littleBit = curTime.tv_usec;
}

double ElapsedTime::Duration(bool reset) {
  struct timeval curTime;
  int retVal;
  retVal = gettimeofday( &curTime, NULL );
  long endBigBit = curTime.tv_sec;
  long endLittleBit = curTime.tv_usec;
  double result = 1000000.0 * (endBigBit - bigBit);
  result += endLittleBit - littleBit;
  result /= 1000000.0;
  if (reset) {
    bigBit = endBigBit;
    littleBit = endLittleBit;
  }
  return result;
}

//----------------- Platform -----------------------------------------------------------------------

ColourDesired Platform::Chrome()
{
  return ColourDesired(0xE0, 0xE0, 0xE0);
}

//--------------------------------------------------------------------------------------------------

ColourDesired Platform::ChromeHighlight()
{
  return ColourDesired(0xFF, 0xFF, 0xFF);
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the currently set system font for the user.
 */
const char *Platform::DefaultFont()
{
  NSString* name = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSFixedPitchFont"];
  return [name UTF8String];
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the currently set system font size for the user.
 */
int Platform::DefaultFontSize()
{
  return [[NSUserDefaults standardUserDefaults] integerForKey: @"NSFixedPitchFontSize"];
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the time span in which two consequtive mouse clicks must occur to be considered as
 * double click.
 *
 * @return
 */
unsigned int Platform::DoubleClickTime()
{
  float threshold = [[NSUserDefaults standardUserDefaults] floatForKey: 
                     @"com.apple.mouse.doubleClickThreshold"];
  if (threshold == 0)
    threshold = 0.5;
  return static_cast<unsigned int>(threshold / kEventDurationMillisecond);
}

//--------------------------------------------------------------------------------------------------

bool Platform::MouseButtonBounce()
{
  return false;
}

//--------------------------------------------------------------------------------------------------

/**
 * Helper method for the backend to reach through to the scintiall window.
 */
long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam) 
{
  return scintilla_send_message(w, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------------------------

/**
 * Helper method for the backend to reach through to the scintiall window.
 */
long Platform::SendScintillaPointer(WindowID w, unsigned int msg, unsigned long wParam, void *lParam)
{
  return scintilla_send_message(w, msg, wParam, (long) lParam);
}

//--------------------------------------------------------------------------------------------------

bool Platform::IsDBCSLeadByte(int codePage, char ch)
{
  // No support for DBCS.
  return false;
}

//--------------------------------------------------------------------------------------------------

int Platform::DBCSCharLength(int codePage, const char* s)
{
  // No support for DBCS.
  return 1;
}

//--------------------------------------------------------------------------------------------------

int Platform::DBCSCharMaxLength()
{
  // No support for DBCS.
  return 2;
}

//--------------------------------------------------------------------------------------------------

int Platform::Minimum(int a, int b)
{
  return (a < b) ? a : b;
}

//--------------------------------------------------------------------------------------------------

int Platform::Maximum(int a, int b) {
  return (a > b) ? a : b;
}

//--------------------------------------------------------------------------------------------------

//#define TRACE
#ifdef TRACE

void Platform::DebugDisplay(const char *s)
{
  fprintf( stderr, s );
}

//--------------------------------------------------------------------------------------------------

void Platform::DebugPrintf(const char *format, ...)
{
  const int BUF_SIZE = 2000;
  char buffer[BUF_SIZE];
  
  va_list pArguments;
  va_start(pArguments, format);
  vsnprintf(buffer, BUF_SIZE, format, pArguments);
  va_end(pArguments);
  Platform::DebugDisplay(buffer);
}

#else

void Platform::DebugDisplay(const char *) {}

void Platform::DebugPrintf(const char *, ...) {}

#endif

//--------------------------------------------------------------------------------------------------

static bool assertionPopUps = true;

bool Platform::ShowAssertionPopUps(bool assertionPopUps_)
{
  bool ret = assertionPopUps;
  assertionPopUps = assertionPopUps_;
  return ret;
}

//--------------------------------------------------------------------------------------------------

void Platform::Assert(const char *c, const char *file, int line)
{
  char buffer[2000];
  sprintf(buffer, "Assertion [%s] failed at %s %d", c, file, line);
  strcat(buffer, "\r\n");
  Platform::DebugDisplay(buffer);
#ifdef DEBUG 
  // Jump into debugger in assert on Mac (CL269835)
  ::Debugger();
#endif
}

//--------------------------------------------------------------------------------------------------

int Platform::Clamp(int val, int minVal, int maxVal)
{
  if (val > maxVal)
    val = maxVal;
  if (val < minVal)
    val = minVal;
  return val;
}

//----------------- DynamicLibrary -----------------------------------------------------------------

/**
 * Implements the platform specific part of library loading.
 * 
 * @param modulePath The path to the module to load.
 * @return A library instance or NULL if the module could not be found or another problem occured.
 */
DynamicLibrary* DynamicLibrary::Load(const char* modulePath)
{
  return NULL;
}

//--------------------------------------------------------------------------------------------------

